﻿/*!
 * ThreeTimesError - Error handling made easy. What, where and how? You decide.
 * Version: 1.1 (20120415_1212)
 * http://code.google.com/p/threetimeserror/
 *
 * Copyright 2012 by Stefan Matsson
 * Licensed under the MIT license (http://www.opensource.org/licenses/mit-license.php)
 */ 
;(function(global, undefined) {
  /*jshint noarg:true, noempty:true, eqeqeq:true, loopfunc:true, bitwise:true, strict:true, undef:true, curly:true, browser:true, maxerr:50, validthis:true */
	"use strict";
	var defaultOptions, // default options
		tteOptions, // the options selected to be used (merge of provided options and default options)
		tte, // the ThreeTimesError object
		has = Object.prototype.hasOwnProperty,
		globalThreeTimesError = global.ThreeTimesError,
		globalTTE = global.TTE,
		globalError = global.onerror,
		intervals = {}; // keeps track of when errors occur to support intervals
		
	// The default gatherer that provides the reporters with the default error data object.
	function errorDataGatherer(options) {
		this.options = options;
	}
	errorDataGatherer.prototype.run = function(obj) {
		return obj[this.options];
	};
	
	// Check if a variable is defined
	// obj - (object) The object
	// returns boolean
	function isDef(obj) {
		return typeof obj !== "undefined";
	}
	
	// Check to see if a variable is a specific type
	// obj - (object) The object
	// type - (string) The type to check for
	// returns boolean
	function isType(obj, type) {
		var x = typeof obj;
		if(x === "object") {
			x = Object.prototype.toString.call(obj);
			x = x.substring(8).replace("]", "");
		}
		return x === type;
	}
	
	// Make a shallow copy of an object
	// obj - (object) The object to clone
	// returns object
	function clone(obj) {
		var x = {}, 
			i;
		if(!isType(obj, "Object")) { return obj; }
		for(i in obj) {
			if(!has.call(obj, i)) { continue; }
			x[i] = obj[i];
		}
		return x;
	}
	
	// Run the specific gatherers
	// gatherers - (array) The gatherers to run
	// error - (object) The error data object
	// result - (object) The object to store the result in
	function runGatherers(gatherers, error, result) {
		var i, x;
		for(i in gatherers) {
			if(!has.call(gatherers, i)) { continue; }
			try { x = gatherers[i].run(error); }
			catch(e) { continue; }
			if(typeof x !== "undefined") { result[i] = x; }
		}
	}
	
	// Run the specific reporters
	// reporters - (array) The reporters to run
	// error - (object) The error data object
	// defaultData - (object) The data gatherered by the default gatherers. 
	function runReporters(reporters, error, defaultData) {
		var i, item, itemData;
		for(i = 0; i < reporters.length; i++) {
			itemData = clone(defaultData);
			item = reporters[i];
			if(item.options && item.options.gatherers) {
				if(item.options.useDefaultGatherers === false) {
					itemData = {};
				}
				runGatherers(item.options.gatherers, error, itemData);
			}
			try { tteOptions.reporters[i].run(itemData); }
			catch(e) { continue; }
		}
	}
	
	// Setup ThreeTimesError
	// options - (object) The options to use. See the documentation for more info.
	function init(options) { 
		var opt = options || defaultOptions,
			i = 0;
		for(i in defaultOptions) {
			if(!has.call(opt, i)) { 
				opt[i] = defaultOptions[i];
			}
		}
		// No reporters specified. No need to continue.
		if(!opt.reporters || opt.reporters.length < 1) {
			// Reset config.
			tteOptions = null;
			return;
		}
		
		tteOptions = opt;
		// Convert to milliseconds
		tteOptions.interval *= 1000;
		
		global.onerror = tte.report;
	}	
	
	// Report an error or other message.
	// There are three ways to use this function:
	// report("message"), 
	// report("message", "url", "line number") or 
	// report(Error) where Error is the object catched by a catch block.
	// This function will automatically be called if an unhandled exception occurrs.
	function report(obj) { 
		var error = {},
			i,
			item,
			lastRun,
			elapsed,
			intervalKey,
			location = global.location.href;
			
		if(!tteOptions) { return; }
			
		// Error reported from the global error event.
		if(arguments.length === 3) {
			error.message = arguments[0];
			error.url = arguments[1] || location;
			error.line = arguments[2];
		}		
		// Reported using a string.
		else if(typeof obj === "string") {
			error.message = obj;
			error.url = location;
		}
		// Reported using try/catch.
		else if(isType(obj, "Error")) {
			error.message = obj.message;
			error.url = location;
		}
		
		// Safari reports the url as the string "undefined".
		error.url = error.url === "undefined" ? location : error.url;
		
		// Check interval
		intervalKey = error.message + "||" + error.url + "||" + error.line;
		lastRun = intervals[intervalKey];
		if(isType(lastRun, "Date")) {
			elapsed = (+new Date()) - (+lastRun);
			if(elapsed < tteOptions.interval) {
				return true;
			}
		}
		intervals[intervalKey] = new Date();
		
		var gd = {};
		// Run default gatherers
		runGatherers(tteOptions.gatherers, error, gd);
		// Report
		runReporters(tteOptions.reporters, error, gd);
		
		return true;
	}
	
	// Release the global TTE and ThreeTimesError variables. 
	// The TTE object will be returned enabling it to be used under a different alias.
	// clearEvents - (boolean) True to release the global on error event.
	// returns object.
	function noConflict(clearEvents) {
		global.TTE = globalTTE;
		global.ThreeTimesError = globalThreeTimesError;
		if(clearEvents) {
			global.onerror = globalError;
		}
		return tte;
	}
	
	// Build TTE object
	tte = {
		reporters: {},
		gatherers: {
			errorData: errorDataGatherer
		},
		report: report,
		init: init,
		noConflict: noConflict
	};
	
	// Set default options
	defaultOptions = {
		reporters: [],
		gatherers: { 
			message: new tte.gatherers.errorData("message"),
			url: new tte.gatherers.errorData("url"),
			line: new tte.gatherers.errorData("line")
		},
		interval: 60
	};
	
	// Expose to global environment
	global.ThreeTimesError = global.TTE = tte;
	
}(this));